/* AndyDroid - Android automation testing Framework.
 Copyright (C) 2011 MicroStrategy Test Automation Team

 AndyDroid is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with software. If not, contact us.
 
 @email:	robort1002@gmail.com
 */
package com.microstrategy.android.automation.utils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import com.microstrategy.android.automation.view.ViewCoordinate;

import junit.framework.Assert;
import android.app.Activity;
import android.app.Instrumentation;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;

/**
 * This class contains view methods. Examples are getViews(),
 * 
 * 
 */
public final class ViewUtils {
	private static final String LOG_TAG = "ViewUtils";

	private ViewUtils() {
		throw new AssertionError();
	}

	/**
	 * Returns views from the shown DecorViews.
	 * 
	 * @param onlySufficientlyVisible
	 *            if only sufficiently visible views should be returned
	 * @return all the views contained in the DecorViews
	 * 
	 */
	public static ArrayList<View> getAllViews(boolean onlySufficientlyVisible) {
		final View[] views = getWindowDecorViews();
		final ArrayList<View> allViews = new ArrayList<View>();
		final View[] nonDecorViews = getNonDecorViews(views);

		if (views != null && views.length > 0) {
			View view;
			for (int i = 0; i < nonDecorViews.length; i++) {
				view = nonDecorViews[i];
				try {
					addChildren(allViews, (ViewGroup) view,
							onlySufficientlyVisible);
				} catch (Exception ignored) {
				}
			}
			view = getRecentDecorView(views);
			try {
				addChildren(allViews, (ViewGroup) view, onlySufficientlyVisible);
			} catch (Exception ignored) {
			}
		}
		return allViews;
	}

	/**
	 * Returns the most recent DecorView
	 * 
	 * @param views
	 *            the views to check
	 * @return the most recent DecorView
	 * 
	 */
	private static final View getRecentDecorView(View[] views) {
		final View[] decorViews = new View[views.length];
		int i = 0;
		View view;

		for (int j = 0; j < views.length; j++) {
			view = views[j];
			if (view.getClass()
					.getName()
					.equals("com.android.internal.policy.impl.PhoneWindow$DecorView")) {
				decorViews[i] = view;
				i++;
			}
		}
		return getRecentContainer(decorViews);
	}

	/**
	 * Returns the most recent view container
	 * 
	 * @param views
	 *            the views to check
	 * @return the most recent view container
	 * 
	 */
	private static final View getRecentContainer(View[] views) {
		View container = null;
		long drawingTime = 0;
		View view;

		for (int i = 0; i < views.length; i++) {
			view = views[i];
			if (view != null && view.isShown() && view.hasWindowFocus()
					&& view.getDrawingTime() > drawingTime) {
				container = view;
				drawingTime = view.getDrawingTime();
			}
		}
		return container;
	}

	/**
	 * Returns all views that are non DecorViews
	 * 
	 * @param views
	 *            the views to check
	 * @return the non DecorViews
	 */
	private static final View[] getNonDecorViews(View[] views) {
		final View[] decorViews = new View[views.length];
		int i = 0;
		View view;

		for (int j = 0; j < views.length; j++) {
			view = views[j];
			if (!(view.getClass().getName()
					.equals("com.android.internal.policy.impl.PhoneWindow$DecorView"))) {
				decorViews[i] = view;
				i++;
			}
		}
		return decorViews;
	}

	/**
	 * Adds all children of {@code viewGroup} (recursively) into {@code views}.
	 * 
	 * @param views
	 *            an {@code ArrayList} of {@code View}s
	 * @param viewGroup
	 *            the {@code ViewGroup} to extract children from
	 * @param onlySufficientlyVisible
	 *            if only sufficiently visible views should be returned
	 * 
	 */
	public static void addChildren(ArrayList<View> views, ViewGroup viewGroup,
			boolean onlySufficientlyVisible) {
		for (int i = 0; i < viewGroup.getChildCount(); i++) {
			final View child = viewGroup.getChildAt(i);

			if (onlySufficientlyVisible && child.isShown()) {
				views.add(child);
			} else if (!onlySufficientlyVisible) {
				views.add(child);
			}

			if (child instanceof ViewGroup) {
				addChildren(views, (ViewGroup) child, onlySufficientlyVisible);
			}
		}
	}

	private static Class<?> windowManager;
	static {
		try {
			windowManager = Class.forName("android.view.WindowManagerImpl");

		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} catch (SecurityException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Returns the WindowDecorViews shown on the screen
	 * 
	 * @return the WindowDecorViews shown on the screen
	 * 
	 */
	private static View[] getWindowDecorViews() {

		Field viewsField;
		Field instanceField;
		try {
			viewsField = windowManager.getDeclaredField("mViews");
			instanceField = windowManager.getDeclaredField("mWindowManager");
			viewsField.setAccessible(true);
			instanceField.setAccessible(true);
			Object instance = instanceField.get(null);
			return (View[]) viewsField.get(instance);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Removes invisible {@code View}s
	 * 
	 * @param viewList
	 *            an {@code ArrayList} with {@code View}s that is being checked
	 *            for invisible {@code View}s.
	 * @return a filtered {@code ArrayList} with no invisible {@code View}s.
	 */
	public static <T extends View> ArrayList<T> removeInvisibleViews(
			ArrayList<T> viewList) {
		ArrayList<T> tmpViewList = new ArrayList<T>(viewList.size());
		for (T view : viewList) {
			if (view != null && view.getVisibility() != View.GONE
					&& view.getVisibility() != View.INVISIBLE) {
				tmpViewList.add(view);
			}
		}
		return tmpViewList;
	}

	/**
	 * Filters views
	 * 
	 * @param classToFilterBy
	 *            the class to filter
	 * @param viewList
	 *            the ArrayList to filter form
	 * @return an ArrayList with filtered views
	 */
	public static <T extends View> ArrayList<T> filterViews(
			Class<T> classToFilterBy, ArrayList<View> viewList) {
		ArrayList<T> filteredViews = new ArrayList<T>(viewList.size());
		for (View view : viewList) {
			if (view != null
					&& classToFilterBy.isAssignableFrom(view.getClass())) {
				filteredViews.add(classToFilterBy.cast(view));
			}
		}
		return filteredViews;
	}

	private static void sleep(int time) {
		try {
			Thread.sleep(time);
		} catch (InterruptedException ignored) {
		}
	}

	public static void scrollInToScreenIfNeeded(Instrumentation inst,
			Activity activity, View view) {
		if (isViewOutOfScreen(activity, view)) {
			scrollIntoScreen(inst, view);
			inst.waitForIdleSync();
		}
	}

	private static void scrollIntoScreen(Instrumentation inst, View view) {
		inst.waitForIdleSync();

		int left = getViewCoordinate(view).getX();
		int top = getViewCoordinate(view).getY();
		int right = left + view.getWidth();
		int bottom = top + view.getHeight();

		Rect rectangle = new Rect(left, top, right, bottom);

		requestRectangleOnScreen(inst, view, rectangle);
	}

	private static void requestRectangleOnScreen(Instrumentation inst,
			final View view, final Rect rectangle) {
		inst.waitForIdleSync();
		inst.runOnMainSync(new Runnable() {

			@Override
			public void run() {
				view.requestRectangleOnScreen(rectangle);
			}
		});
		inst.waitForIdleSync();
	}

	private static boolean isViewOutOfScreen(Activity activity, View view) {
		return (getViewCenter(view).getX() > getScreenWidth(activity)
				|| getViewCenter(view).getY() > getScreenHeight(activity)
				|| getViewCenter(view).getX() < 0 || getViewCenter(view).getY() < 0);
	}

	public static int getScreenHeight(Activity activity) {
		return getWindowManager(activity).getDefaultDisplay().getHeight();
	}

	public static int getScreenWidth(Activity activity) {
		return getWindowManager(activity).getDefaultDisplay().getWidth();
	}

	private static WindowManager getWindowManager(Activity activity) {
		return activity.getWindowManager();
	}

	private static ViewCoordinate getViewCenter(View view) {
		ViewCoordinate coordinate = getViewCoordinate(view);

		int cx = coordinate.getX() + view.getWidth() / 2;
		int cy = coordinate.getY() + view.getHeight() / 2;

		return new ViewCoordinate(cx, cy);
	}

	private static ViewCoordinate getViewCoordinate(View view) {
		int[] xy = new int[2];
		view.getLocationOnScreen(xy);
		return new ViewCoordinate(xy[0], xy[1]);
	}
}